package com.elitesland.org.service.impl;

import cn.hutool.core.util.StrUtil;
import com.elitesland.core.base.BaseModel;
import com.elitesland.core.base.PagingVO;
import com.elitesland.core.exception.BusinessException;
import com.elitesland.org.convert.EmployeeTagConvert;
import com.elitesland.org.entity.EmployeeTagDO;
import com.elitesland.org.entity.EmployeeTagRefDO;
import com.elitesland.org.entity.QEmployeeTagDO;
import com.elitesland.org.param.EmployeeTagQParam;
import com.elitesland.org.repo.EmployeeTagRefRepo;
import com.elitesland.org.repo.EmployeeTagRepo;
import com.elitesland.org.service.EmployeeService;
import com.elitesland.org.service.EmployeeTagService;
import com.elitesland.org.vo.EmployeeTagVO;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import lombok.val;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

@Service
public class EmployeeTagServiceImpl implements EmployeeTagService {

    private EmployeeTagRepo employeeTagRepo;

    private EmployeeTagRefRepo employeeTagRefRepo;

    private EmployeeService employeeService;

    @Autowired
    public void setEmployeeTagRepo(EmployeeTagRepo employeeTagRepo) {
        this.employeeTagRepo = employeeTagRepo;
    }

    @Autowired
    public void setEmployeeTagRefRepo(EmployeeTagRefRepo employeeTagRefRepo) {
        this.employeeTagRefRepo = employeeTagRefRepo;
    }

    @Autowired
    public void setEmployeeService(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    private final QEmployeeTagDO employeeTags = QEmployeeTagDO.employeeTagDO;

    @Override
    public PagingVO<EmployeeTagVO> search(EmployeeTagQParam param) {
        Predicate predicate = employeeTags.isNull().or(employeeTags.isNotNull());

        predicate = StrUtil.isBlank(param.getTagName()) ? predicate :
                ExpressionUtils.and(predicate, employeeTags.tagName.like("%" + param.getTagName() + "%"));

        val ret = employeeTagRepo.findAll(predicate, param.getPageRequest());

        return PagingVO.<EmployeeTagVO>builder()
                .total(ret.getTotalElements())
                .records(ret.getContent().stream().map(EmployeeTagConvert.INSTANCE::doToVO)
                        .collect(Collectors.toList()))
                .build();
    }

    @Override
    public Optional<EmployeeTagVO> oneByTagName(String tagName) {
        return employeeTagRepo.findByTagName(tagName).map(t -> {
            val tag = EmployeeTagConvert.INSTANCE.doToVO(t);

            val refs = employeeTagRefRepo.findAllByTagName(t.getTagName());
            val employeeIds = refs.stream().map(BaseModel::getId).collect(Collectors.toList());

            if (!employeeIds.isEmpty()) {
                val employees = employeeService.listByIds(employeeIds);
                tag.setEmployees(employees);
            }

            return tag;
        });
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public Long create(EmployeeTagVO tag) {
        AtomicLong id = new AtomicLong(0L);
        employeeTagRepo.findByTagName(tag.getTagName()).ifPresentOrElse(
                t -> {
                    throw new BusinessException("记录：" + tag.getTagName() + ", 已存在");
                },
                () -> {
                    val newTag = employeeTagRepo.save(EmployeeTagConvert.INSTANCE.voToDO(tag));
                    id.set(newTag.getId());
                }
        );
        return id.get();
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void update(EmployeeTagVO tag) {
        employeeTagRepo.findByIdNotAndTagName(tag.getId(), tag.getTagName()).ifPresentOrElse(
                t -> {
                    throw new BusinessException("记录：" + tag.getTagName() + ", 已存在");
                },
                () -> {
                    employeeTagRepo.save(EmployeeTagConvert.INSTANCE.voToDO(tag));
                }
        );
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void removeByIds(List<Long> ids) {
        employeeTagRepo.deleteAllByIdIn(ids);
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void bindEmployeesToTag(List<Long> employeeIds, String tag) {
        employeeTagRepo.findByTagName(tag).ifPresent(t -> {
            val oldRefs = employeeTagRefRepo.findAllByTagName(t.getTagName());
            val oldEmployeeIds = oldRefs.stream().map(EmployeeTagRefDO::getEmployeeId)
                    .collect(Collectors.toList());

            val refs = employeeIds.stream()
                    .filter(id -> !oldEmployeeIds.contains(id)).map(id -> {
                        return new EmployeeTagRefDO()
                                .setEmployeeId(id)
                                .setTagName(t.getTagName());
                    })
                    .collect(Collectors.toList());

            employeeTagRefRepo.saveAll(refs);
        });
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void bindTagsToEmployee(List<String> tags, Long employeeId) {
        val oldTags = employeeTagRepo.findAllByTagNameIn(tags);

        val oldTagNames = oldTags.stream().map(EmployeeTagDO::getTagName).collect(Collectors.toList());

        val newTags = tags.stream().filter(t -> !oldTagNames.contains(t)).collect(Collectors.toList());

        if (!newTags.isEmpty()) {
            employeeTagRepo.saveAll(newTags.stream().map(t -> new EmployeeTagDO().setTagName(t))
                    .collect(Collectors.toList()));
        }

        val oldRefs = employeeTagRefRepo.findAllByEmployeeId(employeeId);
        val oldTagNames0 = oldRefs.stream().map(EmployeeTagRefDO::getTagName)
                .collect(Collectors.toList());

        val refs = tags.stream().filter(t -> !oldTagNames0.contains(t)).map(t -> {
            return new EmployeeTagRefDO()
                    .setEmployeeId(employeeId)
                    .setTagName(t);
        }).collect(Collectors.toList());

        employeeTagRefRepo.saveAll(refs);
    }
}
